/*
Copyright (c) 2023 China Mobile Information Technology Co., Ltd
OpenGauss Operator is licensed under Mulan PSL v2.
You can use this software according to the terms and conditions of the Mulan PSL v2.
You may obtain a copy of Mulan PSL v2 at:
         http://license.coscl.org.cn/MulanPSL2
THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
See the Mulan PSL v2 for more details.
*/

package service

import (
	"github.com/pkg/errors"
	"k8s.io/klog/v2"
	gsv1 "openGauss-operator/api/v1"
	customClient "openGauss-operator/internal/client"
	"openGauss-operator/internal/util"
	"reflect"
	"strconv"
	"strings"
	"time"
)

var _ StandbyReconstructInterface = &StandbyReconstruct{}

type StandbyReconstructInterface interface {
	ReconcileStandbyReconstruct(gs *gsv1.OpenGaussCluster) error
}

func NewStandbyReconstruct(client customClient.K8sClient) StandbyReconstructInterface {
	return &StandbyReconstruct{K8sClient: client}
}

type StandbyReconstruct struct {
	K8sClient customClient.K8sClient
}

func (s *StandbyReconstruct) ReconcileStandbyReconstruct(gs *gsv1.OpenGaussCluster) error {

	sr := gs.Spec.StandbyReconstruct
	modifyGs := gs.DeepCopy()
	if util.IsEmptyStruct(sr) || sr.ExecuteInstance == "" || sr.ProblemInstance == "" ||
		sr.Status == "" || sr.Status == util.SrStageFailed || sr.Status == util.SrStageSucceed {
		return nil
	}

	if sr.Status == util.SrStageCreate {

		// 判断ExecuteInstance是否正常，不正常结束
		if err := s.judgeNormalStateWithInstance(sr.ExecuteInstance, gs.Namespace); err != nil {
			modifyGs.Spec.StandbyReconstruct.Status = util.SrStageFailed
			modifyGs.Spec.StandbyReconstruct.Message = err.Error()
			return err
		}

		// 在正常节点执行获取problemInstance的nodeId
		problemInstanceNodeId, err := s.getProblemInstanceNodeId(gs)
		if err != nil {
			modifyGs.Spec.StandbyReconstruct.Status = util.SrStageFailed
			modifyGs.Spec.StandbyReconstruct.Message = err.Error()
			return err
		}
		modifyGs.Spec.StandbyReconstruct.Status = util.SrStageRunning
		now := time.Now()
		if modifyGs.Annotations == nil {
			modifyGs.Annotations = make(map[string]string)
		}
		modifyGs.Annotations[util.SrAnnotationStartTimeKey] = now.Format(util.TimeFormat)

		// 执行备机重建命令
		go s.execStandbyReconstruct(gs, problemInstanceNodeId)
	}
	if sr.Status == util.SrStageRunning {
		startTimeStr := modifyGs.Annotations[util.SrAnnotationStartTimeKey]
		startTime, _ := time.Parse(util.TimeFormat, startTimeStr)
		endTime := startTime.AddDate(0, 0, 1)
		now := time.Now()
		if now.Before(endTime) {
			return nil
		}
		delete(modifyGs.Annotations, util.SrAnnotationStartTimeKey)
		modifyGs.Spec.StandbyReconstruct.Status = util.SrStageFailed
		modifyGs.Spec.StandbyReconstruct.Message = "Time Out " + startTimeStr
	}
	// update StandbyReconstruct
	if !reflect.DeepEqual(gs.Spec.StandbyReconstruct, modifyGs.Spec.StandbyReconstruct) {
		if err := s.K8sClient.UpdateOpenGaussClusterObject(modifyGs, gs); err != nil {
			klog.Error(err)
		}
	}
	return nil
}

func (s *StandbyReconstruct) execStandbyReconstruct(gs *gsv1.OpenGaussCluster, problemInstanceNodeId string) {
	standbyReconstructStatus := util.SrStageRunning
	standbyReconstructMsg := ""
	commandStandbyReconstruct := util.ReplaceData(util.Command(util.CommandStandbyReconstruct), problemInstanceNodeId)
	if _, err := s.K8sClient.ExecCommand(gs.Namespace, commandStandbyReconstruct, gs.Spec.StandbyReconstruct.ExecuteInstance); err != nil {
		standbyReconstructStatus = util.SrStageFailed
		standbyReconstructMsg = err.Error()
	} else {
		standbyReconstructStatus = util.SrStageSucceed
	}
	newGs, err := s.K8sClient.GetOpenGaussCluster(util.GetNamespacedName(gs.Namespace, gs.Name))
	if err != nil {
		klog.Error(err)
		return
	}
	reconstruct := newGs.Spec.StandbyReconstruct
	if util.IsEmptyStruct(reconstruct) || reconstruct.Status != util.SrStageRunning {
		klog.Error(errors.New("Update Standby Reconstruct Error"))
		return
	}
	newModifyGs := newGs.DeepCopy()
	newModifyGs.Spec.StandbyReconstruct.Status = standbyReconstructStatus
	newModifyGs.Spec.StandbyReconstruct.Message = standbyReconstructMsg
	delete(newModifyGs.Annotations, util.SrAnnotationStartTimeKey)

	if err := s.K8sClient.UpdateOpenGaussClusterObject(newModifyGs, newGs); err != nil {
		klog.Error(err)
	}

}

func (s *StandbyReconstruct) getProblemInstanceNodeId(gs *gsv1.OpenGaussCluster) (string, error) {
	sr := gs.Spec.StandbyReconstruct
	problemInstanceNodeId, err := s.K8sClient.ExecCommand(gs.Namespace, util.ReplaceData(util.Command(util.CommandGetDataNodeId), sr.ProblemInstance), sr.ExecuteInstance)
	if err != nil {
		return "", util.ReturnError("Get Instance Node Id Error: ", err)
	}
	problemInstanceNodeId = strings.Replace(problemInstanceNodeId, "\n", "", -1)
	nodeId, err := strconv.Atoi(problemInstanceNodeId)
	if err != nil {
		return "", util.ReturnError("Check Instance Node Id Error: ", err)
	}
	if nodeId < 1 || nodeId > int(*gs.Spec.DataNodeNum) {
		return "", util.ReturnError("Check Instance Node Id Out of range : ", errors.New(problemInstanceNodeId))
	}
	return problemInstanceNodeId, nil
}

func (s *StandbyReconstruct) judgeNormalStateWithInstance(instance string, namespace string) error {
	commandGetInstanceStatus := util.ReplaceData(util.Command(util.CommandGetInstanceStatus), instance)
	executeInstanceState, err := s.K8sClient.ExecCommand(namespace, commandGetInstanceStatus, instance)
	if err != nil {
		return util.ReturnError("Get Instance Status Error: ", err)
	}

	if strings.Replace(executeInstanceState, "\n", "", -1) != util.DnStateNormal {
		return util.ReturnError("Execute Instance DnState Not Normal :", errors.New(executeInstanceState))
	}
	return nil
}
